<- function(f) f(runif(1e3))
randomise randomise(mean)
#> [1] 0.5122965
randomise(mean)
#> [1] 0.5053847
randomise(sum)
#> [1] 498.6696
9 Functionals
Introduction
泛函是一种以函数作为输入,输出向量的函数。下面是一个简单的泛函函数示例:
base R 中常见的apply
家族函数就属于泛函,还有purrr包中的map系列函数,以及一些数学泛函——integrate()
,optim()
。
base R 中的for循环优先级:泛函 > for > while > repeat。如果你对for循环很熟悉,转换到泛函时,只需要从for循环中的提取函数,将其作为参数传入符合要求的泛函即可。当你找不到符合要求的泛函时,首先不要硬适配某种泛函,优先使用for循环,当类似的循环逻辑被重复使用时,考虑编写自己的泛函。
Outline
- 9.2节:介绍
purrr::map()
。 - 9.3节:讲解如何使用多个简单的泛函组合解决一个复杂问题,并且讨论purrr系列函数的使用风格。
- 9.4节:介绍18个
purrr::map()
变体。 - 9.5节:介绍另外一种风格的泛函——
purrr::reduce()
。 - 9.6节:
- 9.7节:介绍 base R 中的泛函。
Prerequisites
本章主要关注purrr包中的泛函,它们有着一致的使用风格,比较容易理解掌握。
library(purrr)
My first functional: map()
purrr::map()
函数接受一个‘list’或‘atomic vector’(.x
)和函数(.f
)作为输入,向量中的每个元素会被应用到函数中,最终返回一个list。即:map(1:3, f)
等价于list(f(1), f(2), f(3))
。
<- function(x) x * 3
triple map(1:3, triple)
#> [[1]]
#> [1] 3
#>
#> [[2]]
#> [1] 6
#>
#> [[3]]
#> [1] 9
这里的map不是“地图”的意思,而是“映射”,意味着‘map’将输入中的向量与结果通过函数进行了映射。
下面是map()
函数的核心逻辑:创建一个与输入等长的list,for循环处理向量,并把结果赋给list的元素。
<- function(x, f, ...) {
simple_map <- vector("list", length(x))
out for (i in seq_along(x)) {
<- f(x[[i]], ...)
out[[i]]
}
out }
为了提高性能,map()
函数其实是用C语言实现的。base R 中的lapply()
函数与purrr::map()
函数类似,但是lapply()
函数不提供下面涉及到的额外功能。
Producing atomic vectors
map()
函数结果返回一个list,这赋予了其极大的灵活性,因为任何数据类型都可以储存在list中。但是有时返回的数据类型足够简单,我们无需再使用list储存。purrr 提供了四种特殊的变体函数——map_lgl()
,map_int()
,map_dbl()
和map_chr()
——分别返回布尔、整数、浮点数和字符向量。
# map_chr() always returns a character vector
map_chr(mtcars, typeof)
#> mpg cyl disp hp drat wt qsec vs
#> "double" "double" "double" "double" "double" "double" "double" "double"
#> am gear carb
#> "double" "double" "double"
# map_lgl() always returns a logical vector
map_lgl(mtcars, is.double)
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE TRUE
# map_int() always returns a integer vector
<- function(x) length(unique(x))
n_unique map_int(mtcars, n_unique)
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> 25 3 27 22 22 29 30 2 2 3 6
# map_dbl() always returns a double vector
map_dbl(mtcars, mean)
#> mpg cyl disp hp drat wt qsec
#> 20.090625 6.187500 230.721875 146.687500 3.596563 3.217250 17.848750
#> vs am gear carb
#> 0.437500 0.406250 3.687500 2.812500
所有map_*()
系列函数要求返回的list长度与输入.x
长度一致,所以.f
必须返回一个值的结果,否则报错。
<- function(x) c(x, x)
pair map_dbl(1:2, pair)
#> Error in `map_dbl()`:
#> ℹ In index: 1.
#> Caused by error:
#> ! Result must be length 1, not 2.
类似的,如果map_*()
系列函数要求返回特定类型的值时,.f
必须返回该类型的结果,否则报错。
map_dbl(1:2, as.character)
#> Error in `map_dbl()`:
#> ℹ In index: 1.
#> Caused by error:
#> ! Can't coerce from a string to a double.
map()
函数则没有上面的要求:
map(1:2, pair)
#> [[1]]
#> [1] 1 1
#>
#> [[2]]
#> [1] 2 2
map(1:2, as.character)
#> [[1]]
#> [1] "1"
#>
#> [[2]]
#> [1] "2"
在 base R 中,有两个函数类似map_*()
系列函数,可以返回原子向量——sapply()
和vapply()
。作者不建议使用sapply()
,因为它会对结果进行整理,导致生成不确定的或向量,或list,或矩阵的结果。相反,vapply()
通过参数FUN.VALUE
要求用户指定结果的数据类型。例如vapply(x, mean, na.rm = TRUE, FUN.VALUE = double(1))
,其等价于map_dbl(x, mean, na.rm = TRUE)
。
Anonymous functions and shortcuts
参数.f
除提供函数名外,更多的是使用匿名函数或~ f(.x, ...)
形式的公式。~ f(.x, ...)
在传递其他参数时十分有用,也是最常用的方法。
map_dbl(mtcars, function(x) length(unique(x)))
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> 25 3 27 22 22 29 30 2 2 3 6
map_dbl(mtcars, ~ length(unique(.x)))
#> mpg cyl disp hp drat wt qsec vs am gear carb
#> 25 3 27 22 22 29 30 2 2 3 6
~ f(.x, ...)
形式的背后是 rlang 支持的lambda表达式。表达式会将map*()
系列函数的参数.x
解析为f()
的第一个参数,参数.y
解析为第二个,依次类推.其他map*()
系列函数未定义,但函数f()
需要的参数会通过...
传递。只有一个参数时可以使用.
替换.x
,但是不建议,.
仅作为向下兼容的牺牲写法,会与其他功能的.
冲突。
as_mapper(~ length(unique(.x)))
#> <lambda>
#> function (..., .x = ..1, .y = ..2, . = ..1)
#> length(unique(.x))
#> attr(,"class")
#> [1] "rlang_lambda_function" "function"
map_*()
系列函数可以用来批量提取数据中的某个元素(通过purrr::pluck()
函数)。通过name信息,位置信息,或二者组合来提取数据,这在从JSON数据(或R对象)中提取数据时特别有用。
<- list(
x list(-1, x = 1, y = c(2), z = "a"),
list(-2, x = 4, y = c(5, 6), z = "b"),
list(-3, x = 8, y = c(9, 10, 11))
)
# Select by name
map_dbl(x, "x")
#> [1] 1 4 8
# Or by position
map_dbl(x, 1)
#> [1] -1 -2 -3
# Or by both
map_dbl(x, list("y", 1))
#> [1] 2 5 9
# You'll get an error if a component doesn't exist:
map_chr(x, "z")
#> Error in `map_chr()`:
#> ℹ In index: 3.
#> Caused by error:
#> ! Result must be length 1, not 0.
# Unless you supply a .default value
map_chr(x, "z", .default = NA)
#> [1] "a" "b" NA
下面是一个提取R t.test 结果对象中P值的示例:
<- map(1:100, ~ t.test(rpois(10, 10), rpois(10, 7)))
trials library(ggplot2)
<- tibble::tibble(p_value = map_dbl(trials, "p.value"))
df_trials
%>%
df_trials ggplot(aes(x = p_value, fill = p_value < 0.05)) +
geom_dotplot(binwidth = .01) + # geom_histogram() as alternative
theme(
axis.text.y = element_blank(),
axis.ticks.y = element_blank(),
legend.position = "top"
)
Passing arguments with ...
诚如上述,你可以使用...
来传递参数给函数。例如na.rm = TRUE
,既可以使用~ f(.x, ...)
形式直接传递参数,也可以通过map函数进行传递。
<- list(1:5, c(1:10, NA))
x map_dbl(x, ~ mean(.x, na.rm = TRUE))
#> [1] 3.0 5.5
map_dbl(x, mean, na.rm = TRUE)
#> [1] 3.0 5.5
注意:...
传入的参数不会被map函数解析,而是直接传递。map函数的其他变体会对参数进行解析,详见9.4节。
注意:使用~ f(.x, ...)
形式直接传递参数和通过map函数进行传递,这两种方式有些许不同。前者会在每次调用函数f
时都评估参数,后者只会在map函数中评估一次。当参数需要延迟评估时要特别注意,例如下面的参数是有runif()
随机生成。
<- function(x, y) x + y
plus
<- c(0, 0, 0, 0)
x map_dbl(x, plus, runif(1))
#> [1] 0.06556413 0.06556413 0.06556413 0.06556413
map_dbl(x, ~ plus(.x, runif(1)))
#> [1] 0.8742589 0.9740036 0.6046076 0.4938412
Argument names
当使用...
传递参数时,推荐使用参数名称,而不是位置。例如,map(x, mean, trim = 0.1)
要比map(x, mean, 0.1)
更好。
map()
函数的参数有两个——输入Vector,函数。考虑到尽量不与函数需要的参数名冲突,purrr 包分别使用了.x
和.f
作为参数名。如果使用了x
,f
(如最前面的simple_map()
)作为参数名,那么就可能导致错误。此时只能使用匿名函数的形式来避免冲突。
<- function(x, f) {
bootstrap_summary f(sample(x, replace = TRUE))
}
simple_map(mtcars, bootstrap_summary, f = mean)
#> Error in mean.default(x[[i]], ...): 'trim' must be numeric of length one
# simple_map(mtcars, f = function(x) bootstrap_summary(x, mean))
base R 中,也有类似.x
和.f
的处理,如:
- aplly 系函数使用大写字母
X
和FUN
作为参数名。 transform()
函数参数前使用前缀_
。
Varying another argument
考虑下图中的情况:你需要将.x
传递给函数的第二个参数,函数的第一个参数是固定的常量。例如你想计算固定向量在不同trim
时的均值,mean(x, trim = trims)
。
<- c(0, 0.1, 0.2, 0.5)
trims <- rcauchy(1000) x
有两种方法可以解决这个问题:
- 使用匿名函数。
map_dbl(trims, ~ mean(x, trim = .x))
#> [1] 62.29917598 0.05500134 0.08526531 0.12600143
map_dbl(trims, function(trim) mean(x, trim = trim))
#> [1] 62.29917598 0.05500134 0.08526531 0.12600143
- 使用
mean()
函数的参数自动匹配。你需要直到mean()
函数的参数名称,
map_dbl(trims, mean, x = x)
#> [1] 62.29917598 0.05500134 0.08526531 0.12600143
Purrr style
在介绍其他map变体函数之前,我们先一窥使用purrr风格的函数的示例——数据分组建模,然后抽取模型系数。
# 切分数据
<- split(mtcars, mtcars$cyl)
by_cyl
# 创建模型
%>%
by_cyl map(~ lm(mpg ~ wt, data = .x)) %>%
map(coef) %>%
map_dbl(2)
#> 4 6 8
#> -5.647025 -2.780106 -2.192438
下面使用 base R 中的函数来实现:
%>%
by_cyl lapply(function(data) lm(mpg ~ wt, data = data)) %>%
lapply(coef) %>%
vapply(function(x) x[[2]], double(1))
#> 4 6 8
#> -5.647025 -2.780106 -2.192438
去除管道符:
<- lapply(by_cyl, function(data) lm(mpg ~ wt, data = data))
models vapply(models, function(x) coef(x)[[2]], double(1))
#> 4 6 8
#> -5.647025 -2.780106 -2.192438
使用for循环:
<- double(length(by_cyl))
slopes for (i in seq_along(by_cyl)) {
<- lm(mpg ~ wt, data = by_cyl[[i]])
model <- coef(model)[[2]]
slopes[[i]]
}
slopes#> [1] -5.647025 -2.780106 -2.192438
有趣的是,从 purr 到 base R 中的 apply 系函数,再到 for 循环,循环函数使用的越少,循环一次做的事情就越多。
Map variants
map系函数主要有23种,从输入与输出两个维度,可以大致划分为下面的表格:
输入\输出 | List | Atomic | Same type | Nothing |
---|---|---|---|---|
One argument | map() |
map_lgl() , … |
modify() |
walk() |
Two arguments | map2() |
map2_lgl() , … |
modify2() |
walk2() |
One argument + index | imap() |
imap_lgl() , … |
imodify() |
iwalk() |
N arguments | pmap() |
pmap_lgl() , … |
— | pwalk() |
从表格中我们可以看到,除了上面介绍过的map()
和map_lgl()
, map_int()
, map_dbl()
, map_chr()
,剩余函数可以再划分为5大类:
map2()
:支持迭代两个输入。imap()
:支持迭代两个输入,其中一个是另外一个的索引。pmap()
:支持迭代多个输入。modify()
:输出类型与输入相同。walk()
:不返回输出。
Same type of output as input: modify()
modify()
函数最常见的使用场景就是修改数据框。map()
虽然也可以修改数据框中的数据,但其结果是一个list,而modify()
函数返回一个数据框。
<- data.frame(
df x = 1:3,
y = 6:4
)
map(df, ~ .x * 2)
#> $x
#> [1] 2 4 6
#>
#> $y
#> [1] 12 10 8
modify(df, ~ .x * 2)
#> x y
#> 1 2 12
#> 2 4 10
#> 3 6 8
modify()
函数的本质可以用下面的函数表示:
<- function(x, f, ...) {
simple_modify for (i in seq_along(x)) {
<- f(x[[i]], ...)
x[[i]]
}
x }
two inputs: map2()
and friends
正如9.2节中提到,当你需要迭代两个参数时,map()
函数无法满足,需要使用map2()
函数。map2()
函数对参数的迭代示意图如下:
假设现在需要计算加权平均值,需要同时迭代xs
,ws
。map_dbl(xs, weighted.mean, w = ws)
会报错,map2_dbl(xs, ws, weighted.mean)
才是正确用法。
<- map(1:8, ~ runif(10))
xs 1]][[1]] <- NA
xs[[<- map(1:8, ~ rpois(10, 5) + 1)
ws
map_dbl(xs, weighted.mean, w = ws)
#> Error in `map_dbl()`:
#> ℹ In index: 1.
#> Caused by error in `weighted.mean.default()`:
#> ! 'x' and 'w' must have the same length
map2_dbl(xs, ws, weighted.mean)
#> [1] NA 0.4274373 0.4235742 0.4496665 0.4655846 0.4480563 0.4670280
#> [8] 0.4991842
传递额外参数na.rm = TRUE
的方式与map()
一样。
map2_dbl(xs, ws, weighted.mean, na.rm = TRUE)
#> [1] 0.4596985 0.4274373 0.4235742 0.4496665 0.4655846 0.4480563 0.4670280
#> [8] 0.4991842
map2()
函数的本质可以用下面的函数表示:
<- function(x, y, f, ...) {
simple_map2 <- vector("list", length(x))
out for (i in seq_along(x)) {
<- f(x[[i]], y[[i]], ...)
out[[i]]
}
out }
map2()
函数与simple_map2()
函数略微不同的地方是,当.x
与.y
长度不一致时,map2()
会自动将短的向量重复补齐。
No outputs: walk()
and friends
map()
函数可以存储数据并输出,但有时我们并不需要返回结果,此时可以使用walk()
函数。下面是循环打印信息的例子,map()
函数返回的是一个都是NULL
的list,walk()
函数则不返回任何对象。
<- function(x) {
welcome cat("Welcome ", x, "!\n", sep = "")
}<- c("Hadley", "Jenny")
names
# As well as generate the welcomes, it also shows
# the return value of cat()
map(names, welcome)
#> Welcome Hadley!
#> Welcome Jenny!
#> [[1]]
#> NULL
#>
#> [[2]]
#> NULL
walk(names, welcome)
#> Welcome Hadley!
#> Welcome Jenny!
walk2()
函数通常用来将数据写入到磁盘。
<- tempfile()
temp dir.create(temp)
<- split(mtcars, mtcars$cyl)
cyls <- file.path(temp, paste0("cyl-", names(cyls), ".csv"))
paths walk2(cyls, paths, write.csv)
dir(temp)
#> [1] "cyl-4.csv" "cyl-6.csv" "cyl-8.csv"
Iterating over values and indices: imap()
base R 中对向量进行循环时,有三种类型:
- 迭代元素:
for (x in xs)
- 迭代元素位置索引:
for (i in seq_along(xs))
- 迭代元素名称索引:
for (nm in names(xs))
map()
函数使用的是第一种,imap()
函数使用第二种和第三种。imap(x, f)
本质上等价于map2(x, names(x), f)
或者map2(x, seq_along(x), f)
。
imap_chr(iris, ~ paste0("The first value of ", .y, " is ", .x[[1]]))
#> Sepal.Length
#> "The first value of Sepal.Length is 5.1"
#> Sepal.Width
#> "The first value of Sepal.Width is 3.5"
#> Petal.Length
#> "The first value of Petal.Length is 1.4"
#> Petal.Width
#> "The first value of Petal.Width is 0.2"
#> Species
#> "The first value of Species is setosa"
<- map(1:6, ~ sample(1000, 10))
x imap_chr(x, ~ paste0("The highest value of ", .y, " is ", max(.x)))
#> [1] "The highest value of 1 is 992" "The highest value of 2 is 944"
#> [3] "The highest value of 3 is 907" "The highest value of 4 is 951"
#> [5] "The highest value of 5 is 880" "The highest value of 6 is 988"
Any number of inputs: pmap() and friends
pmap()
函数的参数有两个:
.l
:参数列表,要求列表中每个元素长度相同,可以看作是一个数据框(data.frame)。.f
:函数。
这里要对map()
函数中的参数.x = list
时作个区分,map()
中列表作为整体是一个参数,而pmap()
中列表中的每个元素是一个参数。
<- tibble::tribble(
params ~n, ~min, ~max,
1L, 0, 1,
2L, 10, 100,
3L, 100, 1000
)
pmap(params, runif)
#> [[1]]
#> [1] 0.8321611
#>
#> [[2]]
#> [1] 29.22384 41.99477
#>
#> [[3]]
#> [1] 203.9812 334.4941 312.3663
map2(x, y, f)
可以等价为pmap(list(x, y), f)
。
pmap_dbl(list(xs, ws), weighted.mean)
#> [1] NA 0.4274373 0.4235742 0.4496665 0.4655846 0.4480563 0.4670280
#> [8] 0.4991842
pmap_dbl(list(xs, ws), weighted.mean, na.rm = TRUE)
#> [1] 0.4596985 0.4274373 0.4235742 0.4496665 0.4655846 0.4480563 0.4670280
#> [8] 0.4991842
9.2.5小节中的情况也可以用pmap()
解决。
<- c(0, 0.1, 0.2, 0.5)
trims <- rcauchy(1000)
x
pmap_dbl(list(trim = trims), mean, x = x)
#> [1] 0.476989476 0.007400580 0.004859345 0.023438581
base R 有两个等价的函数:Map()
和mapply()
,但是它们都有显著的缺陷。
Map()
:你无法提供额外的参数到函数中。mapply()
:与sapply()
一样,无法保证结果的一致性。
Reduce family
reduce系函数比较小众,不仅只有两种变体,还不常见。但它却为代数或处理大型数据提供了一种强有力的解决方法。
Basics
reduce()
函数只接受两个参数:.x
和.f
。参数.f
与map()
不同,它要求初始输入.x
中的两个元素,返回一个与输入类型相同的结果,然后将本次.f
的结果作为下一次调用的输入。即reduce(1:4, f)
等价于f(f(f(1,2),3),4)
。
reduce()
函数的本质可以用下面的函数表示:
<- function(x, f) {
simple_reduce <- x[[1]]
out for (i in seq(2, length(x))) {
<- f(out, x[[i]])
out
}
out }
举一个简单的例子:存在一个全是数字类型的列表,你想找到列表中每个元素都包含的数字。
# 生成一个列表
set.seed(123)
<- map(1:4, ~ sample(1:10, 15, replace = T))
l str(l)
#> List of 4
#> $ : int [1:15] 3 3 10 2 6 5 4 6 9 10 ...
#> $ : int [1:15] 3 8 10 7 10 9 3 4 1 7 ...
#> $ : int [1:15] 10 7 5 7 5 6 9 2 5 8 ...
#> $ : int [1:15] 5 9 10 4 6 8 6 6 7 1 ...
# 使用intersect,找出列表中每个元素都包含的数字
<- l[[1]]
out <- intersect(out, l[[2]])
out <- intersect(out, l[[3]])
out <- intersect(out, l[[4]])
out
out#> [1] 10 5 9
使用reduce()
函数,可以优雅地实现:
reduce(l, intersect)
#> [1] 10 5 9
reduce()
函数通用支持传入额外参数到.f
函数中:
Accumulate
accumulate()
函数是reduce()
函数的变体,它可以很清除地帮助我们看到reduce()
是如何工作地。因为accumulate()
函数会返回一个列表,列表中每个元素是.f
函数的输出。
accumulate(l, intersect)
#> [[1]]
#> [1] 3 3 10 2 6 5 4 6 9 10 5 3 9 9 9
#>
#> [[2]]
#> [1] 3 10 5 4 9
#>
#> [[3]]
#> [1] 10 5 9
#>
#> [[4]]
#> [1] 10 5 9
另外一个理解 reduce 思想地例子是sum()
函数:sum(x)
可以等价为reduce(x,
+)
。accumulate(x,
+)
就等同于累加:
<- c(4, 3, 10)
x reduce(x, `+`)
#> [1] 17
accumulate(x, `+`)
#> [1] 4 7 17
.init
在有.init
参数时,reduce()
的处理逻辑如下:
使用.init
参数有两种作用:
- 当
reduce()
的输入.x
长度为1且没有提供参数.init
,那么reduce()
会直接返回.x
。此时无法对输入类型进行判断,提供了.init
参数后,相当于对输入的数据类型做出了规定。
reduce(1, `+`)
#> [1] 1
reduce("a", `+`)
#> [1] "a"
reduce("a", `+`, .init = 0)
#> Error in .x + .y: non-numeric argument to binary operator
- 当输入
reduce()
的.x
长度为0且没有提供参数.init
,那么reduce()
会直接报错,反之不会。
reduce(integer(), `+`)
#> Error in `reduce()`:
#> ! Must supply `.init` when `.x` is empty.
reduce(integer(), `+`, .init = 0)
#> [1] 0
使用reduce()
函数一定要考虑到.x
数据的长度,和.f
函数的返回值类型。
Multiple inputs
极少数情况下,你需要向reduce()
函数传递两个参数。例如,当你想将多个数据框进行join
,但用于连接的变量.by
因元素而异。此时你可以使用reduce2()
。
reduce2()
的.y
参数的长度取决于是否提供.init
参数:若.x
有4个元素,.f
将只被调用3次,.y
参数的长度是3,若同时提供.init
,.f
将被调用4次,.y
参数的长度是4。
Map-reduce
你可能听说过 map-reduce, 这是一个为 Hadoop 等技术提供动力的概念。现在你可以看到这个基本概念是多么简单和强大:map-reduce 是一个结合了 reduce 的 map。对于大数据来说,区别在于数据分布在多台计算机上。每台计算机对其拥有的数据执行 map, 然后将结果发送回协调器,该协调器将各个结果 reduce 为单个结果。
作为一个简单的例子,想象计算一个非常大的向量的平均值,这个向量如此之大,以至于必须分配给多台计算机。你可以让每台计算机计算总和和长度,然后将这些数据返回给协调器,由协调器通过将总和除以总长度来计算总平均值。
Predicate functionals
predicate 函数,也叫判断函数,返回值是单一的逻辑值——TRUE
或FALSE
,例如is.character()
,is.null()
,all()
,any()
。
is.na()
不符合判断函数的标准,它客户返回一个向量。anyNA()
是判断函数,base R 不提供allNA()
。
Basics
predicate functionals 就是将判断函数应用到.x
中的每个元素,并返回一个逻辑向量。purrr包提过了7个有用的函数,可以分成3组:
some(.x, .p)
:如果有任何元素匹配,返回TRUE
,类似any(map_lgl(.x, .p))
。
every(.x, .p)
:如果所有元素匹配,返回TRUE
,类似all(map_lgl(.x, .p))
。
none(.x, .p)
:如果所有元素不匹配,返回TRUE
,类似all(map_lgl(.x, negate(.p)))
。虽然
any(map_lgl(.x, .p))
与some()
类似,但前者需要将所有元素都判断后再运行any
,后者只要有一个TRUE
就返回。detect(.x, .p)
:返回.x
中第一个匹配的元素。
detect_index(.x, .p)
:返回.x
中第一个匹配的元素的索引。
keep(.x, .p)
:保留所有匹配的元素。
discard(.x, .p)
:丢弃所有匹配的元素。
<- data.frame(x = 1:3, y = c("a", "b", "c"))
df detect(df, is.factor)
#> NULL
detect_index(df, is.factor)
#> [1] 0
str(keep(df, is.factor))
#> 'data.frame': 3 obs. of 0 variables
str(discard(df, is.factor))
#> 'data.frame': 3 obs. of 2 variables:
#> $ x: int 1 2 3
#> $ y: chr "a" "b" "c"
Map variants
map()
和modify()
的有接受一个判断函数的变体——判断函数用来过滤.x
。
<- data.frame(
df num1 = c(0, 10, 20),
num2 = c(5, 6, 7),
chr1 = c("a", "b", "c"),
stringsAsFactors = FALSE
)
str(map_if(df, is.numeric, mean))
#> List of 3
#> $ num1: num 10
#> $ num2: num 6
#> $ chr1: chr [1:3] "a" "b" "c"
str(modify_if(df, is.numeric, mean))
#> 'data.frame': 3 obs. of 3 variables:
#> $ num1: num 10 10 10
#> $ num2: num 6 6 6
#> $ chr1: chr "a" "b" "c"
str(map(keep(df, is.numeric), mean))
#> List of 2
#> $ num1: num 10
#> $ num2: num 6
Base functionals
Matrices and arrays
base R 中的apply()
函数存在一些使用上的陷阱,它更多的是用于矩阵的数值计算上。
与
sapply()
一样,它无法控制返回值的数据类型,向量?矩阵?列表?apply()
函数不是幂等的,这是因为如果汇总函数是标识运算符,则输出并不总是与输入相同。<- matrix(1:20, nrow = 5) a2d <- apply(a2d, 1, identity) a1 identical(a2d, a1) #> [1] FALSE <- apply(a2d, 2, identity) a2 identical(a2d, a2) #> [1] TRUE
apply()
函数不适合处理数据框。<- data.frame(x = 1:3, y = c("a", "b", "c")) df apply(df, 2, mean) #> Warning in mean.default(newX[, i], ...): argument is not numeric or logical: #> returning NA #> Warning in mean.default(newX[, i], ...): argument is not numeric or logical: #> returning NA #> x y #> NA NA
Mathematical concerns
泛函再数学中广泛存在。泛函的计算方式与迭代密切相关。下面是一些 base R 中的计算函数。
integrate()
:计算函数曲线下的面积。uniroot()
:求函数f(x)=0
的根。optimise()
:求函数的最大或最小值。
integrate(sin, 0, pi)
#> 2 with absolute error < 2.2e-14
str(uniroot(sin, pi * c(1 / 2, 3 / 2)))
#> List of 5
#> $ root : num 3.14
#> $ f.root : num 1.22e-16
#> $ iter : int 2
#> $ init.it : int NA
#> $ estim.prec: num 6.1e-05
str(optimise(sin, c(0, 2 * pi)))
#> List of 2
#> $ minimum : num 4.71
#> $ objective: num -1
str(optimise(sin, c(0, pi), maximum = TRUE))
#> List of 2
#> $ maximum : num 1.57
#> $ objective: num 1